#include "gui.h"
#include "gui_mouse.h"
#include "gui_switch.h"
#include "gui_scrollbar.h"
#include <uview.h>
#include <stdlib.h>
#include <malloc.h>
#include <stdio.h>
#include <assert.h>
#include <dotfont.h>

extern dotfont_library_t __gui_dotfont;

void gui_spirit_init(gui_spirit_t *spirit, int x, int y, int width, int height)
{
    list_init(&spirit->list);
    list_init(&spirit->signal_list);
    spirit->type = GUI_SPIRIT_TYPE_UNKNOWN;
    spirit->x = x;
    spirit->y = y;
    spirit->width = width;
    spirit->height = height;
    spirit->width_min = 0;
    spirit->height_min = 0;
    spirit->visiable = 0;
    spirit->max_text_len = -1;  // no limit
    spirit->invisable_char = 0; // no invisable char
    memset(&spirit->style, 0, sizeof(gui_style_t));

    spirit->style.border_color = GUI_COLOR_NONE;
    spirit->style.font_color = GUI_BLACK;
    spirit->style.font_align = GUI_ALIGN_LEFT;
    spirit->text = NULL;
    spirit->image = NULL;
    spirit->surface = NULL;
    spirit->local = NULL;

    spirit->mulline_support = 0; // multiple line text show support

    spirit->style.background_color = GUI_COLOR_NONE;
    spirit->style.background_align = GUI_ALIGN_LEFT;
    spirit->background_image = NULL;

    spirit->style.cursor = GUI_CURSOR_NORMAL; // default cursor

    spirit->container = NULL;
    spirit->attached_container = NULL;
    spirit->view = -1;

    spirit->show_bottom = NULL;
    spirit->show_middle = NULL;
}

gui_spirit_t *gui_spirit_create(int x, int y, int width, int height)
{
    gui_spirit_t *spirit = malloc(sizeof(gui_spirit_t));
    if (!spirit)
        return NULL;
    gui_spirit_init(spirit, x, y, width, height);
    return spirit;
}

int gui_spirit_cleanup(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;
    if (spirit->background_image)
    {
        gui_image_destroy(spirit->background_image);
        spirit->background_image = NULL;
    }
    if (spirit->image)
    {
        gui_image_destroy(spirit->image);
        spirit->image = NULL;
    }
    if (spirit->surface)
    {
        gui_surface_destroy(spirit->surface);
        spirit->surface = NULL;
    }
    if (spirit->text)
    {
        free(spirit->text);
        spirit->text = NULL;
    }
    gui_signal_destroy_all(spirit);
    return 0;
}

int gui_spirit_destroy(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;

    dprintf("[spirit destroy] destroy spirit type %d\n",spirit->type);

    switch (spirit->type)
    {
    case GUI_SPIRIT_TYPE_WINDOW:
        if (gui_check_main_loop())
        {
            // delete event
            if (gui_signal_available(spirit, "delete_event"))
            {
                if (gui_signal_emit_by_name(spirit, "delete_event") < 0)
                    return;
            }
            // destroy event
            gui_signal_emit_by_name(spirit, "destroy_event");
        }
        return gui_window_destroy(spirit);
        break;
    default:
        break;
    }
    if (gui_spirit_cleanup(spirit) < 0)
        return -1;
    free(spirit);
    return 0;
}

int gui_spirit_destroy_all(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;
    gui_container_t *container = spirit->container;
    if (!container)
        return -1;
    gui_spirit_t *tmp, *next;
    list_traversal_all_owner_to_next_safe(tmp, next, &container->children_list, list)
    {
        gui_spirit_destroy(tmp);
    }
    gui_spirit_destroy(tmp);
    return 0;
}

// only used to set absolution position in initilzation state
// plese use gui_spirit_update_pos to update spirit pos in runing state,parame use relative pos
int gui_spirit_set_pos(gui_spirit_t *spirit, int x, int y)
{
    if (!spirit)
        return -1;

    spirit->x = x;
    spirit->y = y;

    return 0;
}

// update sample spirit pos,complex compone please use interface for corrospone spirit
int gui_spirit_update_pos(gui_spirit_t *spirit, int x, int y)
{
    if (!spirit)
        return -1;

    spirit->x += x;
    spirit->y += y;
}

int gui_spirit_set_type(gui_spirit_t *spirit, gui_spirit_type_t type)
{
    if (!spirit)
        return -1;
    spirit->type = type;
    return 0;
}

int gui_spirit_set_view(gui_spirit_t *spirit, int view)
{
    if (!spirit)
        return -1;
    spirit->view = view;
    return 0;
}

int gui_spirit_set_size(gui_spirit_t *spirit, int width, int height)
{
    if (!spirit)
        return -1;
    spirit->width = width;
    spirit->height = height;
    return 0;
}

int gui_spirit_set_size_request(gui_spirit_t *spirit, int width, int height)
{
    if (!spirit)
        return -1;
    spirit->width_min = width;
    spirit->height_min = height;
    switch (spirit->type)
    {
    case GUI_SPIRIT_TYPE_WINDOW:
        gui_window_set_size_minimum((gui_window_t *)spirit, width, height);
        break;
    default:
        break;
    }
    return 0;
}

int gui_spirit_set_text(gui_spirit_t *spirit, char *text)
{
    if (!spirit || !text || !text[0])
        return -1;
    
    int new_len = strlen(text);
    // text too long
    if (spirit->max_text_len > 0 && spirit->max_text_len < new_len)
        new_len = spirit->max_text_len;

    if (!spirit->text)
    {
        spirit->text = malloc(new_len+1);
        memset(spirit->text, 0, new_len+1);
    }
    else
    {
        int old_len = strlen(spirit->text);
        if (old_len < new_len)
        {
            spirit->text = realloc(spirit->text, new_len+1);
            assert(spirit->text);
            memset(spirit->text, 0, new_len+1);
        }
        else
        {
            memset(spirit->text, 0, old_len+1);
        }
    }
    assert(spirit->text);
    strcpy(spirit->text, text);
    spirit->text[new_len] = '\0';
    return 0;
}

int gui_spirit_set_text_max_len(gui_spirit_t *spirit, int maxlen)
{
    if (!spirit || maxlen < 1)
        return -1;
    spirit->max_text_len = maxlen;
    if (spirit->text)
    {
        int new_len = strlen(spirit->text);
        if (spirit->max_text_len > 0 && spirit->max_text_len < new_len)
        {
            new_len = spirit->max_text_len;
            spirit->text[new_len] = '\0';
        }
    }
    return 0;
}

// automatic adjust spirit size adjust text length (only support single line text)
int gui_spirit_auto_size(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;
    if (spirit->text && spirit->style.font_color != GUI_COLOR_NONE)
    {
        int len = strlen(spirit->text);
        dotfont_t *dotfont = dotfont_find(&__gui_dotfont, DOTF_STANDARD_NAME);
        assert(dotfont);
        int w = dotfont_get_char_width(dotfont) * len;
        int h = dotfont_get_char_height(dotfont);
        if(w>spirit->width)
            spirit->width = w + 4;
        if (h > spirit->height)
            spirit->height = h + 4;
    }
    return 0;
}

int gui_spirit_set_background_image(gui_spirit_t *spirit, char *file)
{
    if (!spirit)
        return -1;
    if (!file)
    {
        if (spirit->background_image)
            gui_image_destroy(spirit->background_image);
        spirit->background_image = NULL;
        return 0;
    }
    gui_image_t *img = gui_image_load(file);
    if (!img)
        return -1;
    if (spirit->background_image)
    {
        gui_image_destroy(spirit->background_image);
        spirit->background_image = NULL;
    }
    spirit->image = img;
    return 0;
}

int gui_spirit_set_image(gui_spirit_t *spirit, char *file)
{
    if (!spirit)
        return -1;
    if (!file)
    {
        if (spirit->image)
            gui_image_destroy(spirit->image);
        spirit->image = NULL;
    }
    gui_image_t *img = gui_image_load(file);
    if (!img)
        return -1;
    if (spirit->image)
    {
        gui_image_destroy(spirit->image);
        spirit->image = NULL;
    }
    spirit->image = img;
    return 0;
}

int gui_spirit_set_image2(gui_spirit_t *spirit, char *file,int w,int h)
{
    if (!spirit)
        return -1;
    if (!file)
    {
        if (spirit->image)
            gui_image_destroy(spirit->image);
        spirit->image = NULL;
    }
    gui_image_t *img = gui_image_load2(file,w,h);
    if (!img)
        return -1;
    if (spirit->image)
    {
        gui_image_destroy(spirit->image);
        spirit->image = NULL;
    }
    spirit->image = img;
    return 0;
}

int gui_spirit_set_surface(gui_spirit_t *spirit, gui_surface_t *surface)
{
    if (!spirit)
        return -1;
    if (!surface)
    {
        if (spirit->surface)
            gui_surface_destroy(spirit->surface);
        spirit->surface = NULL;
    }

    if (spirit->surface)
    {
        gui_surface_destroy(&spirit->surface);
        spirit->surface = NULL;
    }
    spirit->surface = surface;
    return 0;
}

int gui_spirit_reset_size(gui_spirit_t *spirit, int width, int height)
{
    if (!spirit)
        return -1;
    // resize surface
    if (spirit->surface)
    {
        if (gui_surface_resize(spirit->surface, width, height) < 0)
            return -1;
    }
    spirit->width = width;
    spirit->height = height;
    return 0;
}

int gui_spirit_set_container(gui_spirit_t *spirit, gui_container_t *container)
{
    if (!spirit)
        return -1;
    if (!container)
    {
        if (spirit->container)
            gui_container_destroy(spirit->container);
        spirit->container = NULL;
    }
    if (spirit->container)
    {
        gui_container_destroy(spirit->container);
        spirit->container = NULL;
    }
    spirit->container = container;
    return 0;
}

int gui_align_calc_pos(gui_style_t *style, gui_align_t align, int box_width, int box_height, int width, int height, int *out_x, int *out_y)
{
    int x, y;
    switch (align)
    {
    case GUI_ALIGN_LEFT:
        x = style->padding;
        y = box_height / 2 - height / 2;
        break;
    case GUI_ALIGN_RIGHT:
        x = box_width - width - style->padding;
        y = box_height / 2 - height / 2;
        break;
    case GUI_ALIGN_TOP:
        x = box_width / 2 - width / 2;
        y = style->padding;
        break;
    case GUI_ALIGN_BOTTOM:
        x = box_width / 2 - width / 2;
        y = box_height - height - style->padding;
        break;
    case GUI_ALIGN_CENTER: // default center
        x = box_width / 2 - width / 2;
        y = box_height / 2 - height / 2;
        break;
    default:
        x = style->padding;
        y = style->padding;
        break;
    }
    *out_x = x;
    *out_y = y;
    return 0;
}

int gui_spirit_calc_align_pos(gui_spirit_t *spirit, int width, int height, int *out_x, int *out_y)
{
    if (!spirit)
        return -1;
    gui_align_calc_pos(&spirit->style, spirit->style.font_align, spirit->width, spirit->height, width, height, out_x, out_y);
    return 0;
}

int gui_spirit_to_surface(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;

    if (!spirit->surface)
    {
        spirit->surface = gui_surface_create(spirit->width, spirit->height);
    }
    else
    {
        // respone resize
        gui_surface_resize(spirit->surface, spirit->width, spirit->height);
    }

    assert(spirit->surface);
    gui_surface_t *surface = spirit->surface;
    int off_x, off_y;
    // background
    if (spirit->style.background_color != GUI_COLOR_NONE)
    {
        gui_surface_rectfill(surface, 0, 0, surface->w, surface->h, spirit->style.background_color);
    }
    // background image
    if (spirit->background_image)
    {

        // calc align pos
        gui_align_calc_pos(&spirit->style, spirit->style.background_align, spirit->width, spirit->height, spirit->background_image->w, spirit->background_image->h, &off_x, &off_y);

        gui_surface_t *srcsurface;
        gui_surface_init(&surface, (uint32_t)spirit->background_image->w, (uint32_t)spirit->background_image->h, (uview_color_t *)spirit->background_image->buff);

        // show to targe rect on surface
        gui_rect_t srcrect = {0, 0, srcsurface->w, srcsurface->h};
        gui_rect_t dstrect = {off_x, off_y, surface->w, surface->h};
        gui_surface_blit(srcsurface, &srcrect, surface, &dstrect);
    }
    // load image
    if (spirit->image)
    {

        // calc align pos
        gui_align_calc_pos(&spirit->style, spirit->style.font_align, spirit->width, spirit->height, spirit->image->w, spirit->image->h, &off_x, &off_y);
        gui_surface_t srcsurface;
        gui_surface_init(&srcsurface, spirit->image->w, spirit->image->h, (uview_color_t *)spirit->image->buff);

        gui_rect_t srcrect = {0, 0, srcsurface.w, srcsurface.h};
        gui_rect_t dstrect = {off_x, off_y, surface->w, surface->h};
        gui_surface_blit(&srcsurface, &srcrect, surface, &dstrect);
    }

    if (spirit->show_middle)
    {
        spirit->show_middle(spirit);
    }

    if (spirit->text && spirit->style.font_color != GUI_COLOR_NONE)
    {
        dotfont_t *dotfont = dotfont_find(&__gui_dotfont, DOTF_STANDARD_NAME);
        assert(dotfont);

        if (!spirit->mulline_support) // no multiple line test
            gui_align_calc_pos(&spirit->style, spirit->style.font_align, spirit->width, spirit->height, dotfont_get_char_width(dotfont) * strlen(spirit->text), dotfont_get_char_height(dotfont), &off_x, &off_y);
        else
        {
            // multiple line text
            if (spirit->style.font_align != GUI_ALIGN_NONE)
            {
                uint32_t w = 0;
                uint32_t h = dotfont_get_char_height(dotfont);

                char *p = spirit->text;
                while (*p)
                {
                    switch (*p)
                    {
                    case '\n':
                        h += dotfont_get_char_height(dotfont);
                        break;
                    default:
                        w += dotfont_get_char_width(dotfont);
                        break;
                    }
                    p++;
                }
                gui_align_calc_pos(&spirit->style, spirit->style.font_align, spirit->width, spirit->height, w, h, &off_x, &off_y);
            }
            else
                gui_align_calc_pos(&spirit->style, spirit->style.font_align, 0, 0, 0, 0, &off_x, &off_y);
        }
        if (spirit->invisable_char)
        {
            gui_text_to_surface_ex(spirit->text, spirit->invisable_char, spirit->style.font_color, DOTF_STANDARD_NAME, surface, off_x, off_y);
        }
        else
        {
            gui_text_to_surface(spirit->text, spirit->style.font_color, DOTF_STANDARD_NAME, surface, off_x, off_y);
        }
    }

    // border
    if (spirit->style.border_color != GUI_COLOR_NONE)
    {
        gui_surface_rect(surface, 0, 0, surface->w, surface->h, spirit->style.border_color);
    }

    if (spirit->show_bottom)
    {
        spirit->show_bottom(spirit);
    }
    return 0;
}

void gui_spirit_adjust_pos_by_type_all(gui_spirit_t *spirit)
{
    gui_container_t *container = spirit->container;
    if (!container)
        return;

    gui_spirit_t *tmp;
    switch (spirit->type)
    {
    case GUI_SPIRIT_TYPE_WINDOW:
    {
        int x = 0;
        int y = 0;
        list_traversal_all_owner_to_next(tmp, &container->children_list, list)
        {
            gui_spirit_set_pos(tmp, x, y);
            x += tmp->width;
        }
    }
    break;

    default:
        break;
    }
}

int gui_spirit_show(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;

    spirit->visiable = 1;
    switch (spirit->type)
    {
    case GUI_SPIRIT_TYPE_WINDOW:
        uview_show(spirit->view);
        return 0;
    default:
        break;
    }

    // default situation,standard widget
    if (!spirit->attached_container)
    {
        dprintf("[gui_spirit] spirit %x type %dd no attach container\n", spirit, spirit->type);
        return -1;
    }

    gui_spirit_t *attached_spirit = (gui_spirit_t *)spirit->attached_container->spirit;
    gui_spirit_to_surface(spirit);

    if (UVIEW_BAD_ID(attached_spirit->view)) // no windows
    {
        dprintf("[gui_spirit] spirit no bind window view\n");
        return -1;
    }

    uview_bitmap_t bmp;
    uview_bitmap_init(&bmp, spirit->surface->w, spirit->surface->h, (uview_color_t *)spirit->surface->pixel);
    if (uview_bitblt_update_ex(attached_spirit->view, spirit->x, spirit->y, &bmp, 0, 0, spirit->width, spirit->height))
    {
        return -1;
    }
    return 0;
}

int gui_spirit_show_children(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;

    gui_container_t *container = spirit->container;
    if (!container)
        return -1;

    gui_spirit_t *tmp;
    list_traversal_all_owner_to_next(tmp, &container->children_list, list)
    {
        gui_spirit_show(tmp);
        // spirit had sub container
        if (tmp->container)
        {
            gui_spirit_show_children(tmp);
        }
    }
    return 0;
}

int gui_spirit_show_all(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;
    if (gui_spirit_show(spirit))
        return -1;
    if (gui_spirit_show_children(spirit) < 0)
        return -1;
    return 0;
}

int gui_spirit_hide(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;

    spirit->visiable = 0;
    // deal with specific
    switch (spirit->type)
    {
    case GUI_SPIRIT_TYPE_WINDOW:
        uview_hide(spirit->view);
        return 0;

    default:
        break;
    }

    // default situation
    if (!spirit->attached_container)
        return -1;
    gui_spirit_t *attached_spirit = (gui_spirit_t *)spirit->attached_container->spirit;
    if (!attached_spirit->surface)
    {
        return -1;
    }

    if (UVIEW_BAD_ID(attached_spirit->view))
    {
        return -1;
    }

    uview_bitmap_t bmp;
    uview_bitmap_init(&bmp, attached_spirit->surface->w, attached_spirit->surface->h, attached_spirit->surface->pixel);
    if (uview_bitblt_update_ex(attached_spirit->view, spirit->x, spirit->y, &bmp, spirit->x-attached_spirit->x, spirit->y-attached_spirit->y, spirit->width, spirit->height))
        return -1;
    return 0;
}

int gui_spirit_hide_all(gui_spirit_t *spirit)
{
    if (!spirit)
        return -1;
    gui_container_t *container = spirit->container;
    if (!container)
        return -1;
    gui_spirit_t *tmp;
    list_traversal_all_owner_to_next(tmp, &container->children_list, list)
    {
        gui_spirit_hide(tmp);
    }
    gui_spirit_hide(tmp);
    return 0;
}
